// 依赖DataGrid
(function($, undefined) {
    // 继承DataGrid
    if ($.fn.DataGrid == undefined) {
        throw "必需先引入DataGrid插件!";
    }
    $.fn.DataTree = function(options) {
        return new $.fn.DataTree.fn.init(options, this);
    };
    $.fn.DataTree.options = function(options) {
        // 有sqlid就转为异步加载节点
        options.async = options.async == null ? !!options.sqlid : options.async;
        $.extend(this, options);
    };
    $.fn.DataTree.options.prototype = {
        key: "id",
        clientKey: "CLIENT_ROW_ID",
        columns: [],
        parentKey: "pid",
        rootId: 0,
        pageNo: 0,
        handlePosition: 0,
        multiSelect: false,
        showRoot: false,
        rootName: "全部",
        deleteChildWithParent: true
    };
    var _extender = function(options) {
        $.extend(this, options);
    };
    _extender.prototype = $.fn.DataGrid.fn;
    $.fn.DataTree.fn = $.fn.DataTree.prototype = new _extender({
        init: function(options, view) {
            var options = new $.fn.DataTree.options(options);
            if (options == undefined) {
                throw "必须指定一个对象参数!";
            }
            if (view == undefined) {
                throw '必须在对象参数中指定view属性!';
            }
            this.options = options;
            this.view = view;
            this.dataset = new DataSet(options.sqlid);
            this._expandedIds = [];
            this._initView(options);
            this._initDataSet(options);
        },
        _initView: function(options) {
            var that = this,
                $tbody;
            $.fn.DataGrid.fn._initView.call(this, options);
            $tbody = this.bodydiv.find("tbody");
            this.bodydiv.on("click", ".treehandle", function(event) {
                var $tr = $(this).closest("tr");
                that.toggleNode($tr);
            });
        },
        _initDataSet: function(options) {
            $.fn.DataGrid.fn._initDataSet.call(this, options);
            this.dataset.merge = options.async;
        },
        beforeOpen: function() {
            this._expandedIds = this.bodydiv.find("tr.expanded:not(.hide)").map(function() {
                return $(this).data("id")
            }).toArray();
            this.options.async && this.bodydiv.find("tbody tr").length == 0 && $.fn.DataGrid.fn.beforeOpen.call(this);
        },
        _render: function(datas, metadatas, aggrs, response) {
            var that = this,
                options = this.options;
            if (response.outdata instanceof Array && options.async && this.bodydiv.find("tbody tr").length > 0) {
                // 插入异步数据
                var byParent = this._parse(response.outdata, true),
                    $ptr,
                    pid,
                    $newNodes;
                for (pid in byParent) {
                    $ptr = this._getTr(pid);
                    this._getChildTrs($ptr).remove();
                    $ptr.after(this._bodyTemplate($.extend({
                        view: this,
                        data: byParent[pid],
                        rootLevel: $ptr.data("level") + 1
                    }, options)));
                    this.toggleNode($ptr, true);
                }
            } else {
                $.fn.DataGrid.fn._render.call(this, this._parse(datas), metadatas, aggrs);
            }
            this._expandedIds = [];
        },
        // 转换数据为树状
        _parse: function(data, groupByParent) {
            var byParent = {},
                parentKey = this.options.parentKey,
                key = this.options.key,
                j = 0,
                result,
                item;
            for (var i = 0; i < data.length; i++) {
                var item = data[i],
                    pid = item[parentKey];
                if (pid in byParent) {
                    byParent[pid].push(item);
                } else {
                    byParent[pid] = [item];
                }
            };
            if (groupByParent) {
                return byParent;
            }
            result = byParent[this.options.rootId] || byParent["undefined"];
            while (item = result[j]) {
                var id = item[key];
                if (id in byParent) {
                    result.splice.apply(result, [j + 1, 0].concat(byParent[id]));
                }
                j++;
            };
            if (this.options.showRoot) {
                var rootNode = {};
                rootNode[parentKey] = rootNode[key] = this.options.rootId;
                result.unshift(rootNode);
            }
            return result;
        },
        _isExpandedBeforeOpen: function(id) {
            return $.inArray(id, this._expandedIds) >= 0;
        },
        _getTr: function(id) {
            return this.bodydiv.find("tbody tr[data-id='" + id + "']");
        },
        _moveTr: function($tr, pid) {
            var oldPid = $tr.data("pid"),
                $ptr = this._getTr(pid),
                oldLevel = $tr.data("level"),
                level,
                $oldPtr = $tr.prev(),
                $trs = this._getChildTrs($tr).add($tr),
                moved = oldPid != pid || $tr.parent().length == 0;
            $tr.data("pid", pid).attr("data-pid", pid);
            if ($ptr.length > 0) {
                level = $ptr.data("level") + 1;
                moved && $ptr.after($trs);
            } else {
                level = 0;
                moved && this.bodydiv.find("tbody").prepend($trs);
            }
            $trs.each(function() {
                var thisLevel = level + $(this).data("level") - oldLevel;
                $(this).data("level", thisLevel).find(".treehandle").css("margin-left", thisLevel + "em");
            });
            this._setTrClass($oldPtr);
            this._setTrClass($ptr);
            this.toggleNode($ptr, true);
        },
        _setTrClass: function($tr) {
            if ($tr.length > 0) {
                var $next = $tr.next();
                $tr[$tr.data("id") !== "" && $next.data("pid") == $tr.data("id") ? "removeClass" : "addClass"]("leaf");
            }
        },
        _getChildTrs: function($tr, depth) {
            var level = $tr.data("level"),
                $trs = $tr.nextUntil(function() {
                    return $(this).data("level") <= level;
                });
            return depth == null ? $trs : $trs.filter(function() {
                return $(this).data("level") - level <= depth;
            });
        },
        onInsert: function(data) {
            var options = this.options,
                pid = data[options.parentKey] || options.rootId,
                $ptr = this._getTr(pid);
            $ptr.after(this._bodyTemplate($.extend({
                view: this,
                data: [data],
                rootLevel: $ptr.data("level") + 1
            }, options)));
            this._setTrClass($ptr);
            this.toggleNode($ptr, true);
            this._setScrollClass();
        },
        onModify: function(data) {
            var options = this.options,
                dataset = this.dataset,
                id = data[options.key],
                newPid = pid = data[options.parentKey],
                parent = null,
                $tr = this._getTr(id).html($(this._bodyTemplate($.extend({
                    view: this,
                    data: [data]
                }, options))).html()),
                oldPid = $tr.data("pid");
            // 检查是否父子是同一个与元素
            if (newPid == id) {
                data[options.parentKey] = oldPid;
                dataset.modifyRecord(data);
            } else {
                // 检测是否调换父子关系
                while (pid != id && (parent = dataset.get(pid))) {
                    pid = parent[options.parentKey];
                }
                if (pid != options.rootId) {
                    var record = {};
                    record[options.key] = newPid;
                    record[options.parentKey] = oldPid;
                    dataset.modifyRecord(record);
                }
                this._moveTr($tr, newPid);
            }
        },
        onDelete: function(data) {
            var options = this.options,
                dataset = this.dataset,
                deleteChildWithParent = options.deleteChildWithParent,
                id = data[options.key],
                pid = data[options.parentKey],
                $tr = this._getTr(id),
                $ptr = $tr.prev(),
                level = $tr.data("level");
            !deleteChildWithParent && this.toggleNode($tr, true);
            this.bodydiv.find("tbody tr[data-pid='" + id + "']").each(function() {
                var record = dataset.get($(this).data("id"));
                if (deleteChildWithParent) {
                    // 同时删除子节点
                    dataset.deleteRecord(record);
                } else {
                    // 修改子节点到上一级
                    record[options.parentKey] = pid;
                    dataset.modifyRecord(record);
                }
            });
            $tr.remove();
            this._setTrClass($ptr);
            this._setScrollClass();
        },
        /* @group 接口 */
        // 打开，关闭节点
        toggleNode: function(id, expand) {
            var $tr = typeof id == "object" ? id : this._getTr(id),
                id = $tr.data("id"),
                pid = $tr.data("pid"),
                hasChild = id == $tr.next().data("pid"),
                expanded = $tr.hasClass("expanded"),
                expand = hasChild && (expand == null ? !expanded : expand),
                level = $tr.data("level"),
                options = this.options,
                $ptr = $tr,
                $bodydiv = this.bodydiv;
            if (!hasChild && options.async) {
                this.asyncNodes(id);
            } else if (expand != expanded && $tr.length > 0) {
                if (expand) {
                    // 上层展开
                    while (!$ptr.hasClass("expanded") && $ptr.length > 0) {
                        $bodydiv.find("tbody tr[data-pid='" + $ptr.data("id") + "']").removeClass("hide");
                        $ptr = $ptr.prevAll("tr[data-id='" + $ptr.addClass("expanded").data("pid") + "']");
                    }
                    // 下层已展开的展开
                    this._getChildTrs($tr).filter(".expanded").each(function() {
                        $bodydiv.find("tbody tr[data-pid='" + $(this).data("id") + "']").removeClass("hide");
                    });
                } else {
                    this._getChildTrs($tr).addClass("hide");
                }
                $tr[expand ? "addClass" : "removeClass"]("expanded");
                this._setScrollClass();
            }
            $tr.removeClass("hide");
        },
        // 异步获取节点
        asyncNodes: function(pid) {
            var that = this,
                $ptr = this._getTr(pid),
                dataset = this.dataset;
            if (pid >= 0) {
                $ptr.addClass("treeloading");
                dataset.params[this.options.parentKey] = pid;
                dataset.open().complete(function() {
                    $ptr.removeClass("treeloading");
                }).done(function() {
                    that._setTrClass($ptr);
                });
            } else {
                that._setTrClass($ptr);
            }
        },
        destroy: function() {
            $.fn.DataGrid.fn.destroy.call(this);
        },
        /* @end 接口 */
        // 模板
        _bodyTemplate: template.compile('<% var path = [],\
            rootLevel = rootLevel || 0,\
            expandLevel = showRoot ? 1:0;\
        for (var j = 0; j < data.length; j++) {\
            var item = data[j],\
                nextItem = data[j+1],\
                id = item[key],\
                pid = item[parentKey],\
                nextPid = nextItem?nextItem[parentKey]:-1,\
                level = path.length + rootLevel,\
                parentExpanded = path.length == 0 || path[path.length - 1].expanded,\
                expanded = parentExpanded && (level < expandLevel || view._isExpandedBeforeOpen(id)); %>\
            <tr data-id="<%= id %>" data-pid="<%= pid %>" data-level="<%= level %>" class="<% if(!async && id != nextPid){ %>leaf<% } %> <% if(!parentExpanded){ %>hide<% }if(expanded){ %>expanded<% } %>">\
            <% for (var i = 0; i < columns.length; i++) {\
                    var cell = columns[i],\
                        edit = cell.edit&&id!=rootId,\
                        value = item[cell.field],\
                        html = id!=rootId?renderValue(value, item, cell.render):(i==handlePosition?rootName:""); %>\
                <td <% if(html!=null&&!/<[^>]+>/.test(html.toString())){ %>title="<%= html %>"<% } %> data-field="<%= cell.field %>" class="<%= cell.cls %> <% if(i==handlePosition){ %>txt-l<% } %> <% if(edit){ %>dataeditable<% } %>">\
                    <% if(i==handlePosition){ %><span class="treehandle noselect" style="margin-left: <%= level %>em;"></span><span class="treeicon"></span><% } %>\
                    <%=# html %>\
                </td>\
            <% }\
                if(nextPid == id) path.push({id:id,expanded:expanded});\
                else if(nextPid != pid) while(path.length > 0 && path[path.length-1].id != nextPid){path.pop()};\
             %>\
            </tr>\
        <% } %>')
    });
    $.fn.DataTree.fn.init.prototype = $.fn.DataTree.fn;
    // 下拉树
    $.fn.DropTree = function(options) {
        return new $.fn.DropTree.fn.init(options, this);
    };
    $.fn.DropTree.options = function(options) {
        options.nameKey = options.nameKey || options.key;
        $.extend(this, options);
    };
    $.fn.DropTree.fn = $.fn.DropTree.prototype = {
        init: function(options, view) {
            var that = this,
                options = this.options = new $.fn.DropTree.options(options),
                $dropdown = $(this._template()).appendTo("body"),
                record;
            this.view = view;
            var datatree = this.datatree = $dropdown.DataTree(options);
            this.dropdown = this.view.DropDown({
                target: $dropdown
            });
            this.datatree.on("click", "tbody tr", function() {
                var data = datatree.getActive(),
                    name = data ? data[options.nameKey] : datatree.options.rootName,
                    value = data ? data[datatree.options.key] : datatree.options.rootId;
                that._setValue(value);
                view.trigger("change", [value, data]);
            });
            this.dataset = this.datatree.dataset;
        },
        _setValue: function(value) {
            var view = this.view,
                datatree = this.datatree,
                dataset = this.dataset,
                record = dataset.get(value),
                name = record ? record[this.options.nameKey] : datatree.options.rootName,
                value = record ? record[datatree.options.key] : datatree.options.rootId;
            view.is("input,select") && view.val(name);
            view.data({
                value: value,
                record: record
            });
            return record;
        },
        setValue: function(value) {
            var record = this._setValue(value);
            this.datatree.toggleNode(value, true);
            record && this.datatree.setActive(record);
        },
        open: function() {
            this.datatree.open();
        },
        close: function() {
            this.datatree.close();
        },
        show: function() {
            this.dropdown.show();
        },
        hide: function() {
            this.dropdown.hide();
        },
        destroy: function() {
            this.datatree.destroy();
            this.dropdown.destroy();
            $(this.dropdown.options.target).remove();
        },
        _template: template.compile('<div class="dropdowndiv hide datagrid noborder handcursor"><table></table></div>')
    }
    $.fn.DropTree.fn.init.prototype = $.fn.DropTree.fn;
    // 下拉ztree
    $.fn.DropZTree = function(options) {
        return new $.fn.DropZTree.fn.init(options, this);
    };
    $.fn.DropZTree.options = function(options) {
        var cors = options.url.indexOf("http") == 0 && (!window.location.host || options.url.indexOf(window.location.host) != 0)
        setting = {
            data: {
                key: {
                    name: options.nameKey
                },
                simpleData: {
                    enable: true,
                    idKey: options.key,
                    pIdKey: options.pIdKey,
                    rootId: 0
                }
            },
            async: {
                enable: true,
                url: options.url,
                autoParam: [options.key + "=" + options.pIdKey],
                otherParam: options.params,
                type: "get",
                dataType: cors ? "jsonp" : "text",
                dataFilter: function(treeId, parentNode, childNodes) {
                    /*var nodes = [];
                    for (k in childNodes) {
                        nodes.push({
                            app_name: childNodes[k],
                            app_id: k,
                            isParent: true
                        });
                    }
                    return nodes;*/
                    for (var i = 0; i < childNodes.length; i++) {
                        childNodes[i].isParent = true;
                    };
                    return childNodes;
                }
            },
            callback: options.callback
        };
        $.extend(this, setting);
    }
    $.fn.DropZTree.fn = $.fn.DropZTree.prototype = {
        init: function(options, view) {
            var options = this.options = new $.fn.DropZTree.options(options),
                that = this,
                $dropdown = $(this._template()).appendTo("body"),
                ztree = this.ztree = $.fn.zTree.init($dropdown, options),
                setting = ztree.setting;
            setting.callback.onClick = function(event, treeId, treeNode) {
                var name = treeNode[setting.data.key.name],
                    value = treeNode[setting.data.simpleData.idKey];
                view.val(name).data({
                    value: value,
                    record: treeNode
                }).trigger("change", [value, treeNode]);
            }
            this.view = view;
            this.dropdown = this.view.DropDown({
                target: $dropdown,
                preventClose: ".switch"
            });
        },
        open: function() {
            this.datatree.open();
        },
        close: function() {
            this.datatree.close();
        },
        show: function() {
            this.dropdown.show();
        },
        hide: function() {
            this.dropdown.hide();
        },
        destroy: function() {
            this.datatree.destroy();
            this.dropdown.destroy();
            this.dropdown.view.remove();
        },
        _template: template.compile('<div class="ztree dropdowndiv hide"></div>')
    }
    $.fn.DropZTree.fn.init.prototype = $.fn.DropZTree.fn;
})(jQuery);